/*
 * _______________#########_______________________ 
 * ______________############_____________________ 
 * ______________#############____________________ 
 * _____________##__###########___________________ 
 * ____________###__######_#####__________________ 
 * ____________###_#######___####_________________ 
 * ___________###__##########_####________________ 
 * __________####__###########_####_______________ 
 * ________#####___###########__#####_____________ 
 * _______######___###_########___#####___________ 
 * _______#####___###___########___######_________ 
 * ______######___###__###########___######_______ 
 * _____######___####_##############__######______ 
 * ____#######__#####################_#######_____ 
 * ____#######__##############################____ 
 * ___#######__######_#################_#######___ 
 * ___#######__######_######_#########___######___ 
 * ___#######____##__######___######_____######___ 
 * ___#######________######____#####_____#####____ 
 * ____######________#####_____#####_____####_____ 
 * _____#####________####______#####_____###______ 
 * ______#####______;###________###______#________ 
 * ________##_______####________####______________ 
 */

/*
 * @Author: 火红色祥云 809880192@qq.com
 * @Date: 2022-06-23 19:21:13
 * @LastEditors: 火红色祥云 809880192@qq.com
 * @LastEditTime: 2022-10-06 20:13:53
 * @FilePath: /imx6ull-app/13_asyncnoti/13_asyncnoti.c
 * @Description: 异步通知
 */
#include <linux/irq.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/module.h>

#include <linux/ide.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/io.h>

#include <linux/wait.h>
#include <linux/poll.h>

#include <linux/fcntl.h>
#include <linux/signal.h>

#define KEYIRQCNT   1
#define KEYIRQNAME  "asyncnoti"
#define KEY0VALUE   0x01
#define INVAKEY     0xFF
#define KEYNUM      1


struct ST_KEYIRQ
{
    int gpio;
    int irqnum;
    unsigned char value;
    char name[10];
    irqreturn_t (*handler)(int, void *);
};

struct ST_DEVIRQ
{
    dev_t devid;
    int major;
    int minor;
    struct device_node  *st_node;
    struct cdev st_cdev;
    struct class *st_class;
    struct device *st_device;
    struct timer_list st_timer;

    atomic_t keyvalue;
    atomic_t releasekey;
    unsigned char curkeyvalue;

    struct ST_KEYIRQ st_keyirq[KEYNUM];

    wait_queue_head_t r_wait;

    struct fasync_struct *st_async_queue;
};

struct ST_DEVIRQ    st_DevIrq;

/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: keyirq_open
* function: 
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
int keyirq_open(struct inode *nd, struct file *fp)
{
    fp->private_data = &st_DevIrq;
    return 0;
}

/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: keyirq_read
* function: 
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
ssize_t keyirq_read(struct file *fp, char __user *buf, size_t len, loff_t *offset)
{
    int retval=0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;

    struct ST_DEVIRQ *dev = (struct ST_DEVIRQ *)fp->private_data;

    if (fp->f_flags & O_NONBLOCK)       //非阻塞访问
    {
        if (atomic_read(&dev->releasekey) == 0)     //没有按键按下
        {
            return -EAGAIN;
        }
        
    }
    else
    {
        //加入等待队列，等待被唤醒，也就是有按键按下
        retval = wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
        if (retval)
        {
            goto wait_error;
        }
    }

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) { /* 有按键按下 */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			retval = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
	} else {
		goto data_error;
	}

    return 0;  
wait_error:
    return retval;
data_error:
    return -EINVAL;
}

/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: keyirq_poll
* function: 
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
unsigned int keyirq_poll(struct file *fp, struct poll_table_struct *wait)
{
    unsigned int mask;

    struct ST_DEVIRQ *dev = (struct ST_DEVIRQ *)fp->private_data;

    //poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
    poll_wait(fp,&dev->r_wait,wait);

    if (atomic_read(&dev->releasekey))
    {
        mask = POLLIN | POLLRDNORM;
    }
    return mask;
}

/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: keyirq_fasync
* function: 
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
int keyirq_fasync(int fd, struct file *fp, int on)
{
    struct ST_DEVIRQ *dev = (struct ST_DEVIRQ *)fp->private_data;

    return fasync_helper(fd,fp,on,&dev->st_async_queue);//初始化异步通知结构体
}
/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: keyirq_realease
* function: 
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
int keyirq_realease(struct inode *nd, struct file *fp)
{
    return keyirq_fasync(-1,fp,0);//关闭异步通知
}


struct file_operations keyirq_fops = {
    .owner = THIS_MODULE,
    .open = keyirq_open,
    .read = keyirq_read,
    .poll = keyirq_poll,
    .fasync = keyirq_fasync,
    .release = keyirq_realease,
};

/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: key0_handler
* function: 中断服务函数
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
static irqreturn_t key0_handler(int irq,void *dev_id)
{
    struct ST_DEVIRQ *dev = (struct ST_DEVIRQ *)dev_id;

    dev->curkeyvalue = 0;
    dev->st_timer.data = (volatile long)dev_id;
    mod_timer(&dev->st_timer,jiffies+msecs_to_jiffies(10));
    return IRQ_RETVAL(IRQ_HANDLED);
}

/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: timer_function
* function: 定时器处理函数 消抖
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct ST_KEYIRQ *st_key;

    struct ST_DEVIRQ *st_dev = (struct ST_DEVIRQ *)arg;

    num = st_dev->curkeyvalue;
    st_key = &st_dev->st_keyirq[num];

    value = gpio_get_value(st_key->gpio);
    if (value == 0)
    {
        atomic_set(&st_dev->keyvalue,st_key->value);
    }
    else
    {
        atomic_set(&st_dev->keyvalue,0x80 | st_key->value);
        atomic_set(&st_dev->releasekey,1);//标记松开按键
    }

    if (atomic_read(&st_dev->releasekey))      //一次完整的按键过程
    {
        if (st_dev->st_async_queue)
        {
            kill_fasync(&st_dev->st_async_queue,SIGIO,POLL_IN);//向应用程序发送信号
        }
        
    }
    
}
/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: keyio_init
* function: 
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
static int keyio_init(void)
{
    int i=0;
    int retval=0;
    st_DevIrq.st_node = of_find_node_by_path("/lux_key");
    if (st_DevIrq.st_node == NULL)
    {
        printk("node lux_key not find\r\n");
        return -1;
    }
    
    for (i = 0; i < KEYNUM; i++)
    {
        st_DevIrq.st_keyirq[i].gpio = of_get_named_gpio(st_DevIrq.st_node,"key-gpio",0);
        if (st_DevIrq.st_keyirq[i].gpio < 0)
        {
            printk("can not find %d gpio\r\n",i);
        }
    }
    
    for (i = 0; i < KEYNUM; i++)
    {
        memset(st_DevIrq.st_keyirq[i].name,0,sizeof(st_DevIrq.st_keyirq[i].name));
        sprintf(st_DevIrq.st_keyirq[i].name,"KEY%d",i);
        gpio_request(st_DevIrq.st_keyirq[i].gpio,st_DevIrq.st_keyirq[i].name);
        gpio_direction_input(st_DevIrq.st_keyirq[i].gpio);
        st_DevIrq.st_keyirq[i].irqnum = irq_of_parse_and_map(st_DevIrq.st_node,i);
        printk("gpio=%d\tirqNum:%d\r\n",st_DevIrq.st_keyirq[i].gpio,st_DevIrq.st_keyirq[i].irqnum);
    }
    
    st_DevIrq.st_keyirq[0].handler = key0_handler;
    st_DevIrq.st_keyirq[0].value = KEY0VALUE;

    for ( i = 0; i < KEYNUM; i++)
    {
        /*
        request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
        */
        retval = request_irq(
            st_DevIrq.st_keyirq[i].irqnum,
            st_DevIrq.st_keyirq[i].handler,
            IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
            st_DevIrq.st_keyirq[i].name,
            &st_DevIrq
        );

        if (retval < 0)
        {
            printk("irq %d request failed\r\n",st_DevIrq.st_keyirq[i].irqnum);
            return -EFAULT;
        }
        
    }
    init_timer(&st_DevIrq.st_timer);
    st_DevIrq.st_timer.function = timer_function;

    //wait_queue_head_t *q
    init_waitqueue_head(&st_DevIrq.r_wait);

    return 0;
}

/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: keyirq_init
* function: 
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
static int __init keyirq_init(void)
{

    alloc_chrdev_region(&st_DevIrq.devid,0,KEYIRQCNT,KEYIRQNAME);
    st_DevIrq.major = MAJOR(st_DevIrq.devid);
    st_DevIrq.minor = MINOR(st_DevIrq.minor);
    printk("Major:%d\rMinor:%d\r\n",st_DevIrq.major,st_DevIrq.minor);

    st_DevIrq.st_cdev.owner = THIS_MODULE;
    cdev_init(&st_DevIrq.st_cdev,&keyirq_fops);
    cdev_add(&st_DevIrq.st_cdev,st_DevIrq.devid,KEYIRQCNT);

    st_DevIrq.st_class = class_create(THIS_MODULE,KEYIRQNAME);
    st_DevIrq.st_device = device_create(st_DevIrq.st_class,NULL,st_DevIrq.devid,NULL,KEYIRQNAME);

    atomic_set(&st_DevIrq.keyvalue,INVAKEY);
    atomic_set(&st_DevIrq.releasekey,0);

    keyio_init();

    return 0;
}

/**************************************************
* Copyright (c) 2022-2030, Lux.Co.Ltd
* filename: keyirq_exit
* function: 
* param   : 
* return  : 
* auther  : Lux
* Date    : 2022-10-6
* Version : V1.0
**************************************************/
static void __exit keyirq_exit(void)
{
    int i = 0;
    del_timer_sync(&st_DevIrq.st_timer);

    for ( i = 0; i < KEYNUM; i++)
    {
        //void free_irq(unsigned int, void *);
        free_irq(st_DevIrq.st_keyirq[i].irqnum,&st_DevIrq);
    }
    
    cdev_del(&st_DevIrq.st_cdev);
    unregister_chrdev_region(st_DevIrq.devid,KEYIRQCNT);
    device_destroy(st_DevIrq.st_class,st_DevIrq.devid);
    class_destroy(st_DevIrq.st_class);
}

module_init(keyirq_init);
module_exit(keyirq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lux");

